﻿//Orginal date made: 3rd March 2012
//Creator: by Luke Bermingham

//Last modified: 18th Apr
//Modifier name: Luke Bermingham

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Windows;
using SharedResources.Model;

namespace DataLoadingModule.ViewModel.LoadDataSet
{
    class DatasetParser
    {

        #region setup

        //This class reads the data from the loaded dataset and parses it
        //according to the user input
        private List<Type> _datatypeOrder;
        private Dictionary<int, string> _dataOrder;
        public DatasetParser(Dictionary<int, string> dataOrder, string pathOfFileToParse)
        {
            _dataOrder = dataOrder;
            _pathOfFileToParse = pathOfFileToParse;
            _datatypeOrder = new List<Type>();
            foreach (var key in dataOrder.Keys)
            {
                var valueToExamine = dataOrder[key];
                valueToExamine = valueToExamine.ToLower();

                if (valueToExamine.Contains("object") || valueToExamine.Contains("trajectory"))
                {
                    _datatypeOrder.Insert(key, typeof(Int32));
                }

                else if (valueToExamine.Contains("lati") || valueToExamine.Contains("longi") || valueToExamine.Contains("coord"))
                {
                    _datatypeOrder.Insert(key, typeof(Double));
                }

                else if (valueToExamine.Contains("time") || valueToExamine.Contains("date"))
                {
                    _datatypeOrder.Insert(key, typeof(DateTime));
                }
            }
        }

         //these represent the highest and lowest x and y values in the dataset
         //they are populated when the data is parsed
        public double MaxX { get; private set; }
        public double MinX { get; private set; }
        public double MaxY { get; private set; }
        public double MinY { get; private set; }


        private string _pathOfFileToParse;
        private char _delimiter;
        SortedGroupedDataset parsedOutput = new SortedGroupedDataset(); //the parsed output

#endregion 

        #region parsing

        public SortedGroupedDataset TryParse(char delimiter, BackgroundWorker parentThread)
        {
            _delimiter = delimiter;
            if (string.IsNullOrEmpty(_pathOfFileToParse))
            {
                throw new Exception("Could not read the file, it was null or empty.");
            }

            //Get num of rows in dataset to calculate the percentage completed
            int totalLines;
            try
            {
                totalLines = File.ReadAllLines(_pathOfFileToParse).Length;
                if(totalLines < 1)
                {
                   throw new Exception("The file had one or less lines.");
                }
            }
            catch(Exception e)
            {
                throw new Exception("The file could not be read." + e.Message);
            }

            var datasetFile = new StreamReader(_pathOfFileToParse); //make a file reader
            FindBoundingCoordinates(datasetFile, delimiter); //get the minx, max x, min y, max y
            parsedOutput.SetBoundingCoordinates(MaxX, MaxY, MinX, MinY); //set the bounds
            datasetFile.Close(); //close the handle
            datasetFile = new StreamReader(_pathOfFileToParse); //make another one for the rest of the data

            var numLinesRead = 0.0;

            for (int i = 0; i < totalLines; i++)
            {
                
                var percentComplete = (numLinesRead / totalLines) * 100;
                int percent = (int)percentComplete;

                //report the progress to the worker to update the ui progress bar
                var valueToTry = percent.ToString(CultureInfo.InvariantCulture);
                if (_percentagesDone.Contains(valueToTry) == false && MultipleOfTen(valueToTry)) //if it ends with a zero
                {//have to do this check otherwise it tries to update the 0(n) times instead of every 10%
                    _percentagesDone.Add(valueToTry);
                    parentThread.ReportProgress(percent);
                }

                var singleLine = datasetFile.ReadLine(); //read a line
                //anow inflate the data being read
                var stPoint = InflateRecordedData(singleLine); //spatio-temporal point
                if (stPoint != null)
                {
                    parsedOutput.Add(stPoint);  //read the line and inflate it
                    numLinesRead++;
                }
            }
            datasetFile.Close(); //close it otherwise can get memory problems and data popping in
            parentThread.ReportProgress(100);
            return parsedOutput;
        }

        private List<String> _percentagesDone = new List<string>(); //has is recorded 10%, 20% etc

        private bool MultipleOfTen(string someValue)
        {
            if (someValue.Equals("0"))
            {
                return false;
            }
            return someValue.EndsWith("0");
        }

#endregion

        #region Bounding Coordinates

        private void FindBoundingCoordinates(StreamReader fileToRead, char delimiter)
        {
            //coordinate positioning
            var coordinatePositions = new Dictionary<String, int>
                                          {
                                              {"latitude", 0},
                                              {"longitude", 0},
                                              {"xcoord", 0},
                                              {"ycoord", 0}
                                          };

            for (var i = 0; i < _dataOrder.Values.Count; i++)
            {
                var valueBeingAnalysed = _dataOrder[i].ToLower();
                if (valueBeingAnalysed.Contains("lati"))
                {
                    coordinatePositions["latitude"] = i;
                }
                else if (valueBeingAnalysed.Contains("long"))
                {
                    coordinatePositions["longitude"] = i;
                }
                else if (valueBeingAnalysed.Contains("x coord"))
                {
                    coordinatePositions["xcoord"] = i;
                }
                else if (valueBeingAnalysed.Contains("y coord"))
                {
                    coordinatePositions["ycoord"] = i;
                }
            }


            while(!fileToRead.EndOfStream) //while not at the end
            {
                var lineRead = fileToRead.ReadLine();

                if (lineRead == null) continue;
                var splitString = lineRead.Split(delimiter);
                if (splitString[0].Equals(lineRead) == false && splitString.Length > 2)
                {
                    //as long as it was delimited and its length is greater than 2

                    Boolean latitudeIsset = false;
                    Double latitude = 0.0;

                    Boolean longitudeIsset = false;
                    Double longitude = 0.0;

                    Boolean xCoordIsset = false;
                    Double xCoord = 0.0;

                    Boolean yCoordIsset = false;
                    Double yCoord = 0.0;

                    if(Double.TryParse(splitString[coordinatePositions["latitude"]], out latitude))
                    {
                        latitudeIsset = true;
                        if(Double.TryParse(splitString[coordinatePositions["longitude"]], out longitude))
                        {
                            longitudeIsset = true;
                            //if(Double.TryParse(splitString[coordinatePositions["x coord"]], out xCoord))
                            //{
                            //    xCoordIsset = true;
                            //    if(Double.TryParse(splitString[coordinatePositions["y coord"]], out yCoord))
                            //    {
                            //        yCoordIsset = true;
                            //    }
                            //} IMPLEMENT THIS WHEN USER CAN CHOOSE WHICH ONE THEY WANT
                        }
                    }
                    if(latitudeIsset && longitudeIsset)
                    {
                        SetIfBounding(longitude, latitude);
                    }
                    //else if(xCoordIsset && yCoordIsset) if xy chosen and isset
                    //{
                    //    SetIfBounding(xCoord, yCoord);
                    //}
                }
            }
        }

        private void SetIfBounding(double longitude, double latitude)
        {
            //lat = y
            //lon = x
            if(longitude > MaxX)
            {
                MaxX = longitude;
                if(Math.Abs(MinX - 0.0) < Precision.EPSILON)
                {
                    MinX = longitude;
                }
            }
            else if(longitude < MinX)
            {
                MinX = longitude;
            }

            if(latitude > MaxY)
            {
                MaxY = latitude;
                if (Math.Abs(MinY - 0.0) < Precision.EPSILON)
                {
                    MinY = latitude;
                }
            }
            else if(latitude < MinY)
            {
                MinY = latitude;
            }
        }

        #endregion

        #region InflateData

        private SpatioTemporalPoint InflateRecordedData(string singleLineOfData)
        {
            var delimitedData = singleLineOfData.Split(_delimiter);
            if(delimitedData[0].Equals(singleLineOfData) || delimitedData.Length < 2)
            {
                //if it wasn't delimited or if its length no grater than 2
                return null;
            }

            Int32 objectId = 0;
            Boolean objIdIsset = false;

            Int32 trajectoryId;
            Boolean trajIdIsset = false;

            DateTime date = new DateTime();
            Boolean dateIsset = false;

            DateTime time = new DateTime();
            Boolean timeIsset = false;

            Double latitude = 0;
            Boolean latitudeIsset = false;

            Double longitude = 0;
            Boolean longitudeIsset = false;

            Double xCoord = 0;
            Boolean xCoordIsset = false;

            Double yCoord = 0;
            Boolean yCoordIsset = false;


            DateTime combinedDateAndTime;
            for (int i = 0; i < delimitedData.Length; i++)
            {
                if(objIdIsset && dateIsset && timeIsset && longitudeIsset && latitudeIsset)
                { //try to make the file early
                    combinedDateAndTime = date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second);
                    return new SpatioTemporalPoint(objectId, combinedDateAndTime, latitude, longitude);
                }
                if(objIdIsset && dateIsset && timeIsset && xCoordIsset && yCoordIsset)
                { //try again if x and y are intialise but not long lat
                    combinedDateAndTime = date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second);
                    return new SpatioTemporalPoint(objectId, combinedDateAndTime, yCoord, xCoord);
                }

                Type typeToMakeInto = _datatypeOrder[i];
                var stringType = _dataOrder[i].ToLower(); //the string representation of the data being analysed
                if(typeToMakeInto == typeof(Int32))
                {
                    //make it into an int32
                    try
                    {
                        Int32 result;
                        Int32.TryParse(delimitedData[i], out result);
                        
                        if(stringType.Contains("object"))
                        {
                            objectId = result;
                            objIdIsset = true;
                        }
                        else if(stringType.Contains("trajectory"))
                        {
                            trajectoryId = result;
                            trajIdIsset = true;
                        }
                    }
                    catch(Exception)
                    {
                        System.Diagnostics.Debug.WriteLine("Skipping over " + delimitedData[i] + "could not convert it to an Int32");
                        break; //don't continue any further
                    }
                }
                else if(typeToMakeInto == typeof(Double))
                {
                    //make into a double
                    try
                    {
                        Double result;
                        Double.TryParse(delimitedData[i], out result);
                        if (stringType.Contains("longi"))
                        {
                            longitude = result;
                            longitudeIsset = true;
                        }
                        else if (stringType.Contains("lati"))
                        {
                            latitude = result;
                            latitudeIsset = true;
                        }
                        else if(stringType.Contains("x coord"))
                        {
                            xCoord = result;
                            xCoordIsset = true;
                        }
                        else if(stringType.Contains("y coord"))
                        {
                            yCoord = result;
                            yCoordIsset = true;
                        }
                    }
                    catch(Exception)
                    {
                        System.Diagnostics.Debug.WriteLine("Skipping over " + delimitedData[i] + "could not convert it to an Double");
                        break; //don't continue any further
                    }
                }
                else if(typeToMakeInto == typeof(DateTime))
                {
                    //make into a double
                    try
                    {
                        DateTime result;
                        DateTime.TryParse(delimitedData[i], out result);
                        if (stringType.Contains("date"))
                        {
                            date = result;
                            dateIsset = true;
                        }
                        else if(stringType.Contains("time"))
                        {
                            time = result;
                            timeIsset = true;
                        }
                    }
                    catch (Exception)
                    {
                        System.Diagnostics.Debug.WriteLine("Skipping over " + delimitedData[i] + "could not convert it to a DateTime");
                        break; //don't continue any further
                    }
                }
            }
            //all the data had to be read to intliase
            combinedDateAndTime = date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second);
            return new SpatioTemporalPoint(objectId, combinedDateAndTime, latitude, longitude);
        }

        #endregion
    }
}
